/****************************************************************************
 * Copyright (c) 2010 Torkild U. Resheim
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Torkild U. Resheim  - initial API and implementation
 *****************************************************************************/
package no.resheim.buildmonitor.ui.views;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.regex.Matcher;

import no.resheim.buildmonitor.core.data.Build;
import no.resheim.buildmonitor.core.data.Job;
import no.resheim.buildmonitor.core.data.Job.Health;
import no.resheim.buildmonitor.core.data.Server;
import no.resheim.buildmonitor.ui.BuildmonitorUI;
import no.resheim.buildmonitor.ui.properties.JobPropertySource;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.preference.JFacePreferences;
import org.eclipse.jface.resource.FontRegistry;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.CellLabelProvider;
import org.eclipse.jface.viewers.DecorationOverlayIcon;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;

/**
 * 
 * {@link CellLabelProvider} is apparently deprecated/old API but it appears
 * that the only way we can get fancy cell tool tips is to use this one.
 * 
 * @author Torkild Ulvøy Resheim
 * @since 1.0
 */
class JobViewLabelProvider extends CellLabelProvider {

	private class TreeItemUpdater extends Thread {

		private final ArrayList<TreeItem> list;
		private final ArrayList<TreeItem> removable;

		private void addItem(TreeItem item) {
			synchronized (list) {
				list.add(item);
			}
		}

		private void removeItem(TreeItem item) {
			synchronized (list) {
				list.remove(item);
			}
		}

		private TreeItemUpdater() {
			list = new ArrayList<TreeItem>();
			removable = new ArrayList<TreeItem>();
		}

		private Display getDisplay() {
			return view.getViewer().getControl().getDisplay();
		}

		@Override
		public void run() {
			int delay = 10; // decent default value
			int[] delays = BuildmonitorUI.getDefault().getProgressDelay();
			while (!view.getViewer().getTree().isDisposed()) {
				long currentTime = System.currentTimeMillis();
				// The plug-in may not have initialised yet.
				if (delays.length > 0) {
					delay = delays[progress];
				}
				final long pause = currentTime + delay * 10;
				while (pause > System.currentTimeMillis()) {
					try {
						Thread.sleep(pause - System.currentTimeMillis());
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				if (!view.getViewer().getControl().isDisposed()) {
					getDisplay().asyncExec(new Runnable() {
						@Override
						public void run() {
							progress++;
							if (progress >= BuildmonitorUI.getDefault()
									.getProgressImageCount()) {
								progress = 0;
							}
							// Root nodes are servers
							synchronized (list) {
								for (TreeItem item : list) {
									if (!item.isDisposed()) {
										Object o = item.getData();
										if (o != null && o instanceof Job) {
											Job j = (Job) o;
											if (j.isActive()) {
												item.setImage(BuildmonitorUI
														.getDefault()
														.getImageRegistry()
														.get(BuildmonitorUI.PROGRESS
																+ "_"
																+ progress));
											} else {
												removable.add(item);
												item.setImage(getColumnImage(o,
														0));
											}
										}
									}
								} // for
							}
							for (TreeItem item : removable) {
								removeItem(item);
							}
							removable.clear();
						}
					});
				}
			}
		}
	}

	private static final String CSS_FONT_FAMILY = "font-family";
	private static final String CSS_FONT_SIZE = "font-size";
	private static final String EMPTY_STRING = "";
	/** Keeps track of fonts that we use */
	private static FontRegistry fontRegistry = new FontRegistry();
	/**
	 * Basic HTML for rendering the tooltip.
	 */
	private static final String html = "<body style=\"margin:4px;font-family:'${font-family}';font-size:${font-size}px\">${content}</body>";
	private static final String TEXT_FONT = "org.eclipse.ui.workbench.TAB_TEXT_FONT";

	private final HashMap<String, String> contentProperties;

	/**
	 * Progress indicator position.
	 */
	private int progress = 0;

	private TreeItemUpdater updater;

	/** Shows all the build jobs */
	private final JobsView view;

	public JobViewLabelProvider(JobsView view) {
		this.view = view;
		contentProperties = new HashMap<String, String>();
		updateFromPreferences();
		installPainter();
	}

	public Color getBackground(Object element, int columnIndex) {
		return null;
	}

	public Image getColumnImage(Object obj, int index) {
		switch (index) {
		case 0:
			if (obj instanceof Server) {
				return getDecoratedImage(BuildmonitorUI.getDefault()
						.getImageRegistry(), BuildmonitorUI.IMG_SERVER,
						((Server) obj).getStatus());
			}
			if (obj instanceof Job) {
				Job j = (Job) obj;
				Health h = j.getHealth();
				if (j.isActive()) {
					return BuildmonitorUI.getDefault().getImageRegistry()
							.get(BuildmonitorUI.PROGRESS + "_" + progress);

				}
				if (h != null) {
					return BuildmonitorUI.getDefault().getImageRegistry()
							.get(j.getHealth().name());
				}
			}
		}
		return null;
	}

	private static final Point size = new Point(16, 16);

	private Image getDecoratedImage(ImageRegistry registry, String baseImageId,
			IStatus status) {
		ImageDescriptor overlay = null;
		Image baseImage = registry.get(baseImageId);
		switch (status.getSeverity()) {
		case IStatus.ERROR:
			overlay = PlatformUI.getWorkbench().getSharedImages()
					.getImageDescriptor(ISharedImages.IMG_DEC_FIELD_ERROR);
			break;
		case IStatus.WARNING:
			overlay = PlatformUI.getWorkbench().getSharedImages()
					.getImageDescriptor(ISharedImages.IMG_DEC_FIELD_WARNING);
			break;
		default:
			return baseImage;
		}
		// Construct a new image identifier
		String decoratedImageId = baseImageId.concat(String.valueOf(status
				.getSeverity()));
		// Return the stored image if we have one
		if (registry.get(decoratedImageId) == null) {
			// Otherwise create a new image and store it
			DecorationOverlayIcon decoratedImage = new DecorationOverlayIcon(
					baseImage, new ImageDescriptor[] { null, null, null,
							overlay, null }, size) {
			};
			registry.put(decoratedImageId, decoratedImage);
		}
		return registry.get(decoratedImageId);
	}

	/**
	 * Returns the column text of the object. Note that the index is not
	 * necessarily the same as the display order.
	 * 
	 * @param obj
	 *            the object to render a label for
	 * @param index
	 *            the index of the column
	 * @return
	 */
	public String getColumnText(Object obj, int index) {
		if (obj instanceof Build) {
			Build j = (Build) obj;
			if (index == 0)
				return j.getURI().toString();
		}
		if (obj instanceof Server) {
			Server s = (Server) obj;
			switch (index) {
			case 0:
				return s.getName();
			case 1:
				return EMPTY_STRING;
			case 2:
				return EMPTY_STRING;
			}

		}
		if (obj instanceof Job) {
			Job j = (Job) obj;
			Object id = view.getViewer().getTree().getColumn(index).getData();
			if (id != null) {
				Object value = JobPropertySource.getPropertyValue(j, id);
				if (value != null)
					return value.toString();
			}
		}
		return EMPTY_STRING;
	}

	private String getDescriptionHTML(Job job) {
		String result = html;
		contentProperties.put("content", job.getDescription());
		for (String property : contentProperties.keySet()) {
			String replacement = contentProperties.get(property);
			Assert.isNotNull(replacement, "Illegal value in property \""
					+ property + "\"");
			String value = Matcher.quoteReplacement(contentProperties
					.get(property));
			result = result.replaceAll("\\$\\{" + property + "\\}", value); //$NON-NLS-1$ //$NON-NLS-2$

		}
		return result;
	}

	public Font getFont(Object element, int columnIndex) {
		if (element instanceof Job) {
			Job j = (Job) element;
			if (j.isActive()) {
				return fontRegistry.getBold(Display.getCurrent()
						.getSystemFont().getFontData()[0].getName());
			}
		}
		return null;
	}

	public Color getForeground(Object element, int columnIndex) {
		if (element instanceof Job) {
			Job j = (Job) element;
			Health h = j.getHealth();
			if (h != null && h.equals(Health.DISABLED)) {
				return JFaceResources.getColorRegistry().get(
						JFacePreferences.QUALIFIER_COLOR);
			}
		}
		return null;
	}

	@Override
	public int getToolTipDisplayDelayTime(Object object) {
		return 1000;
	}

	@Override
	public Point getToolTipShift(Object object) {
		return new Point(5, 5);
	}

	@Override
	public String getToolTipText(Object element) {
		if (element instanceof Job) {
			Job job = (Job) element;
			return getDescriptionHTML(job);
		}
		if (element instanceof Server) {
			Server server = (Server) element;
			if (server.getStatus().isOK()) {
				return server.getDescription();
			}
			return server.getStatus().getMessage()
					+ server.getStatus().getException();
		}
		return super.getToolTipText(element);
	}

	@Override
	public int getToolTipTimeDisplayed(Object object) {
		return 0;
	}

	/**
	 * We use this method to hook into the paint method.
	 */
	private void installPainter() {
		updater = new TreeItemUpdater();
		updater.start();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.eclipse.jface.viewers.CellLabelProvider#update(org.eclipse.jface.
	 * viewers.ViewerCell)
	 */
	@Override
	public void update(ViewerCell cell) {
		int index = cell.getColumnIndex();
		Object element = cell.getElement();
		cell.setText(getColumnText(element, index));
		Image image = getColumnImage(element, index);
		cell.setImage(image);
		cell.setBackground(getBackground(element, index));
		cell.setForeground(getForeground(element, index));
		cell.setFont(getFont(element, index));
		// If the job is building we want to add it to the list of items that
		// gets their image updated to indicate progress.
		if (element instanceof Job && ((Job) element).isActive()) {
			updater.addItem((TreeItem) cell.getItem());
		}
	}

	/**
	 * Updates the content property set with values from preference settings.
	 */
	private void updateFromPreferences() {
		FontData[] fd = JFaceResources.getFont(TEXT_FONT).getFontData();

		contentProperties.put(CSS_FONT_FAMILY, fd[0].getName());
		contentProperties.put(CSS_FONT_SIZE, String.valueOf(fd[0].getHeight()));
	}

}